package org.groovymud.shell.command;

/* Copyright 2008 Matthew Corby-Eaglen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;
import groovy.util.ScriptException;

import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.security.auth.Subject;

import org.apache.log4j.Logger;
import org.groovymud.object.Container;
import org.groovymud.object.MudObject;
import org.groovymud.object.alive.Alive;
import org.groovymud.object.alive.Player;
import org.groovymud.object.registry.MudObjectAttendant;
import org.groovymud.scripting.utils.BindingDelegate;
import org.groovymud.shell.command.handler.CommandException;
import org.groovymud.shell.command.handler.CommandHandler;
import org.groovymud.shell.security.MudPermission;
import org.springframework.beans.factory.annotation.Autowired;

public class CommandInterpreter {

    private BindingDelegate delegate;
    private GroovyScriptEngine groovyScriptEngine;

    private static final Logger logger = Logger.getLogger(CommandInterpreter.class);

    private String mudSpace;
    private String scriptSpace;
    private String store;

    private Map<String, String> commandAliases;

    private List<CommandHandler> commandHandlers;

    class ArgHolder {

        String command;
        String args;

        public void parse(String playerCommand, MudObject object) {
            playerCommand = resolveAlias(playerCommand);
            if (playerCommand.indexOf(' ') != -1) {
                // fill the args string
                args = playerCommand.substring(playerCommand.indexOf(' ') + 1);
                // return the next command as the first arg
                command = playerCommand.substring(0, playerCommand.indexOf(' '));
            } else {
                // or we return the resolved command
                command = playerCommand;
            }
            command = resolveAlias(command);
        }
    }

    public void doShellCommand(String command, final Player player) {
        if (command == null) {
            throw new IllegalArgumentException("command cannot be null");
        }

        StringTokenizer all = new StringTokenizer(command, ";");
        while (all.hasMoreTokens()) {
            String nextCommand = all.nextToken();

            final ArgHolder argHolder = new ArgHolder();

            argHolder.parse(nextCommand, player);

            // run through a privileged action so we don't access stuff we
            // shouldn't
            Subject subject = player.getSubject();
            Subject.doAsPrivileged(subject, new PrivilegedAction<Object>() {

                public Object run() {
                    Exception error = null;
                    try {
                        return doCommand(argHolder.command, argHolder.args, player);
                    } catch (Exception e) {
                        logger.error("Cannot do command " + argHolder.command + " with args " + argHolder.args, e);
                        error = e;
                    } finally {
                        if (error != null) {
                            try {
                                player.getTerminalOutput().writeln("You cannot do that.");
                            } catch (IOException e) {
                                logger.error(e, e);
                            }
                        }
                    }
                    return null;

                }
            }, null);
        }

    }

    protected String resolveAlias(String nextCommand) {
        // check the player's list of aliases for commands
        // check the standard set of aliases for commands
        String alias = getCommandAliases().get(nextCommand);
        return alias == null ? nextCommand : alias;
    }

    public Object doCommand(final String command, String argsAsString, MudObject performingObject) throws CommandException {
        return doCommand(command, argsAsString, performingObject, true);
    }

    public Object doCommand(final String command, String argsAsString, MudObject performingObject, boolean checkEnvironment) throws CommandException {
        Container container = performingObject.getCurrentContainer();
        boolean commandRun = false;
        MudObject containerMO = (MudObject) container;
        if (checkEnvironment) {
            // the object means to do the action with the container, it
            // holds a list of
            // local commands that need to be run.
            commandRun = containerMO.doCommand((Alive) performingObject, command, argsAsString);

        }
        if (!commandRun) {
            commandRun = checkCommandHandlers(command, argsAsString, performingObject, commandRun);
        }
        if (!commandRun) {
            return runCommandScript(command, argsAsString, performingObject, commandRun);
        }
        return null;
    }

    protected boolean checkCommandHandlers(final String command, String argsAsString, MudObject performingObject, boolean commandRun) throws CommandException {
        for (CommandHandler handler : getCommandHandlers()) {
            commandRun = handler.doCommand((Alive) performingObject, command, argsAsString);
            if (commandRun) {
                break;
            }
        }
        return commandRun;
    }

    protected Object runCommandScript(final String command, String argsAsString, MudObject performingObject, boolean commandRun) throws CommandException {

        try {
            Class<?> clz = getGroovyScriptEngine().loadScriptByName(command + ".groovy");
            if (performingObject instanceof Player) {
                MudPermission permission = new MudPermission(clz.getName());
                AccessController.checkPermission(permission);
            }
            List<String> argsAsList = null;

            if (argsAsString != null) {
                argsAsList = Arrays.asList(argsAsString.split(" "));
            }

            Map<String, Object> bindingMap = new HashMap<String, Object>();

            bindingMap.put("source", performingObject);
            bindingMap.put("args", argsAsList);
            bindingMap.put("argstr", argsAsString);

            Binding binding = getDelegate().createGroovyScriptEngineBinding(bindingMap);

            return getGroovyScriptEngine().run(command + ".groovy", binding);
        } catch (ResourceException e) {
            throw new CommandException("error running command script: " + command + ".groovy", e);
        } catch (ScriptException e) {
            throw new CommandException("error running command script: " + command + ".groovy", e);
        }
    }


    public GroovyScriptEngine getGroovyScriptEngine() {
        return groovyScriptEngine;
    }

    public void setGroovyScriptEngine(GroovyScriptEngine gsEngine) {
        this.groovyScriptEngine = gsEngine;
    }

    public Map<String, String> getCommandAliases() {
        return commandAliases;
    }

    public void setCommandAliases(Map<String, String> aliases) {
        this.commandAliases = new HashMap<String, String>(aliases);
    }

    public String getMudSpace() {
        return mudSpace;
    }

    public void setMudSpace(String mSpace) {
        this.mudSpace = mSpace;
    }

    public void setScriptSpace(String scrSpace) {
        this.scriptSpace = scrSpace;
    }

    public String getScriptSpace() {
        return scriptSpace;
    }

    /**
     * @return the object persistence store directory
     */
    public String getStore() {
        return store;
    }

    /**
     * @param store - return the object persistence store directory
     */
    public void setStore(String store) {
        this.store = store;
    }

    /**
     * @param delegate the delegate to set
     */
    public void setDelegate(BindingDelegate delegate) {
        this.delegate = delegate;
    }

    /**
     * @return the delegate
     */
    public BindingDelegate getDelegate() {
        return delegate;
    }

    /**
     * @param commandHandlers the commandHandlers to set
     */
    public void setCommandHandlers(List<CommandHandler> commandHandlers) {
        this.commandHandlers = commandHandlers;
    }

    /**
     * @return the commandHandlers
     */
    public List<CommandHandler> getCommandHandlers() {
        return commandHandlers;
    }

    public void registerCommandHandler(CommandHandler handler) {
        getCommandHandlers().add(handler);
    }

    public void removeCommandHandler(CommandHandler handler) {
        getCommandHandlers().remove(handler);
    }
}
